﻿using System;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using cludg.Infrastructure.Core.Logging;
using nsadv.Statistic.Domain;
using nsadv.Statistic.Services.Security;
using nsadv.Statistic.Web.Security;
using nsadv.Statistic.Web.ViewModels;
using cludg.Infrastructure.Core.Attributes;
using cludg.Infrastructure.Core.Settings;
using Membership = nsadv.Statistic.Services.Security.Membership;

namespace nsadv.Statistic.Web.Controllers
{
	public class BaseController : Controller, IAuthentication
	{
		protected IAuthentication Authehtication { get { return this; } }

		[Dependency]
		public Membership Membership { get; set; }
		[Dependency]
		public ILogger Logger { get; set; }
		[Dependency]
		public ISettings Settings { get; set; }

		[HttpPost]
		public ActionResult Confirm(string result, string action, string back)
		{
			if (result == "yes")
			{
				return Redirect(action);
			}
			return Redirect(back);
		}

		#region Current user
		protected internal User GetCurrentUser(bool anonymouseSafe = true)
		{
			User user = null;

			string id = Authehtication.AuthenticatedUserId;
			if (!string.IsNullOrEmpty(id))
			{
				user = Membership.GetUserById(id);
				if (user == null)
				{
					Logger.Info(string.Format("Error Authenticated User ID = {0}", id));
					Authehtication.SignOut();
				}
			}

			if ((user == null) && anonymouseSafe)
				user = Membership.Anonymouse;

			return user;
		}

		protected internal string GetCurrentUserId()
		{
			var id = Authehtication.AuthenticatedUserId;
			if (string.IsNullOrEmpty(id))
				id = Membership.Anonymouse.Id;
			return id;
		}
		#endregion

		#region Menu
		protected virtual void InitMenu(BaseModel model)
		{
			if (model.SecurityInfo.User.Is(UserRoles.ReportViewers))
			{
				model.NavigationMenu.Items.Add(
			            new MenuItem
			            {
			                Id = "ActiveBanners",
			                Caption = "Действующие баннеры",
			                Url = "/"
			            });
				model.NavigationMenu.Items.Add(
			            new MenuItem
			            {
			                Id = "ArchiveBanners",
			                Caption = "Архив баннеров",
			                Url = "/?archive=true"
			            });
			}
			if (model.SecurityInfo.User.Is(UserRole.Root))
			{
				model.NavigationMenu.Items.Add(
			            new MenuItem
			            {
			                Id = "UsersManagement",
			                Caption = "Управление пользователями",
			                Url =  UrlHelper.GenerateUrl(null,
								"Users",
								"Administration",
								null,
								RouteTable.Routes,
								Request.RequestContext,
								true)
			            });
			}
		}

		#endregion

		#region Create model
		protected virtual void InitSecurityInfo(SecurityInfo info)
		{
			info.User = GetCurrentUser();

			if (info.User == Membership.Anonymouse)
				info.IsAnonymouse = true;
		}

		protected virtual void InitModel<TModel>(TModel model) where TModel : BaseModel
		{
			InitSecurityInfo(model.SecurityInfo);
			InitMenu(model);

			model.MainSiteUrl = Settings["MainSiteUrl"] as string;
		}


		protected virtual TModel CreateModel<TModel>() where TModel : BaseModel, new()
		{
			TModel model = new TModel();

			InitModel(model);

			return model;
		}
		#endregion

		#region Authehtication
		public void SignIn(string userId, bool remember)
		{
			FormsAuthentication.SetAuthCookie(userId, remember);
		}

		public void SignOut()
		{
			FormsAuthentication.SignOut();
		}

		public string AuthenticatedUserId
		{
			get
			{
				return User == null ? string.Empty : User.Identity.Name;
			}
		}
		#endregion

		// TODO: Возможность отключения логгирования на уровне настроек (не только на уровне логгера)

		#region Logging

		private static string BuildActionDescription(ActionDescriptor descriptor)
		{
			var desc = new StringBuilder();

			desc.Append(descriptor.ActionName);
			desc.Append("(");
			var idx = 0;
			foreach (var parameterDescriptor in descriptor.GetParameters())
			{
				if (idx > 0)
				{
					desc.Append(", ");
				}
				desc.Append(parameterDescriptor.ParameterType.FullName);
				desc.AppendFormat(" {0}", parameterDescriptor.ParameterName);
				idx++;
			}
			desc.Append(")");

			var custAttributes = descriptor.GetCustomAttributes(true);
			if (custAttributes.Length > 0)
			{
				desc.Append(", Attributes [");
				var i = 0;
				foreach (var custAttribute in custAttributes)
				{
					if (i > 0)
					{
						desc.Append(", ");
					}
					desc.Append(custAttribute);
					i++;
				}
				desc.Append("]");
			}

			return desc.ToString();
		}

		#endregion

		#region Events
		protected override void OnActionExecuting(ActionExecutingContext filterContext)
		{
			Logger.DebugFormat("CONTROLLER	{0} INVOKE {1}",
				this, BuildActionDescription(filterContext.ActionDescriptor));

			string error;
			if (!CheckActionAccess(filterContext.ActionDescriptor, out error))
				filterContext.Result = AccessDenied(error);
			else
			{
				base.OnActionExecuting(filterContext);
			}
		}

		protected override void OnActionExecuted(ActionExecutedContext filterContext)
		{
			base.OnActionExecuted(filterContext);

			// Init message box
			var model = filterContext.Controller.ViewData.Model as BaseModel;
			if (model != null)
			{
				InitAlertMessage(model.Alert);
			}
		}

		protected override void OnResultExecuting(ResultExecutingContext filterContext)
		{
			Logger.DebugFormat("CONTROLLER	'{0}' BEGIN '{1}' result returning...",
				this, filterContext.Result);

			base.OnResultExecuting(filterContext);
		}

		protected override void OnResultExecuted(ResultExecutedContext filterContext)
		{
			base.OnResultExecuted(filterContext);

			// Clear message box
			var model = filterContext.Controller.ViewData.Model as BaseModel;
			if (model != null)
			{
				ClearMessage();
			}

			Logger.DebugFormat("CONTROLLER	'{0}' END '{1}' result returning.",
			this, filterContext.Result);
		}

		protected override void OnException(ExceptionContext filterContext)
		{
			Logger.Error(string.Format("CONTROLLER	'{0}' EXCEPTION.",
				this), filterContext.Exception);

			base.OnException(filterContext);
		} 
		#endregion

		#region Security
		protected ActionResult AccessDenied(string error, string backUrl = null)
		{
			//if (backUrl == null)
			//{
			//    backUrl = Request.UrlReferrer != null ? Request.UrlReferrer.ToString() : string.Empty;
			//}

			//var model = CreateModel<AccessDeniedModel>();
			//model.Error = error;
			//model.BackUrl = backUrl;

			//ShowMessage(MessageType.Error, string.Format("Доступ к действию запрещен./n{0}", error));

			return RedirectToAction("Cabinet", "Statistic", null);

			//return View("AccessDenied", model);
		}

		protected internal bool CurrentUserIs(UserRole role)
		{
			return GetCurrentUser().Role == role;
		}

		protected bool CheckAccessDenied(UserRole role, string error, string backUrl, out ActionResult result)
		{
			result = null;

			if (CurrentUserIs(role))
				return true;

			if (backUrl == null)
			{
				backUrl = Request.UrlReferrer != null ? Request.UrlReferrer.ToString() : string.Empty;
			}
			result = AccessDenied(error, backUrl);

			return false;
		}

		protected bool CheckAccessDenied(UserRole role, string error, out ActionResult result)
		{
			return CheckAccessDenied(role, error, null, out result);
		}

		protected bool CheckActionAccess(ActionDescriptor action, out string error)
		{
			foreach (var pr in action.GetCustomAttributes(typeof(RoleRequired), true).Cast<RoleRequired>())
			{
				if (!CurrentUserIs(pr.Role))
				{
					error = pr.Error;
					return false;
				}
			}
			error = null;
			return true;
		}
		#endregion

		#region Alerts

		protected const string AlertTypeSessionVar = "AlertType";
		protected const string AlertMessageSessionVar = "AlertMessage";

		protected void ClearMessage()
		{
			Session[AlertTypeSessionVar] = MessageType.None;
			Session[AlertMessageSessionVar] = string.Empty;
		}

		protected void ShowMessage(MessageType type, string message)
		{
			Session[AlertTypeSessionVar] = type;
			Session[AlertMessageSessionVar] = message;
		}

		protected void InitAlertMessage(Alert alert)
		{
			if (Session[AlertTypeSessionVar] != null)
			{
				alert.Type = (MessageType)Session[AlertTypeSessionVar];
			}
			if (Session[AlertMessageSessionVar] != null)
			{
				alert.Message = (string)Session[AlertMessageSessionVar];
			}
		}

		#endregion

		protected string GetModelStateErrors()
		{
			var errors = new StringBuilder();
			foreach (var state in ModelState)
			{
				if (state.Value != null)
				{
					foreach (var error in state.Value.Errors)
					{
						errors.AppendLine(string.Format("- {0}", error.ErrorMessage));
					}
				}
			}
			return errors.ToString();
		}
	}
}
